
import 'dart:convert';
import 'dart:typed_data';

class ByteBuf {

  int _writeOffset = 0;
  int _readOffset = 0;
  int _size = 1024;
  ByteData _byteBuffers;
  int _markReadIndex = -1;
  int _markWriterIndex = -1;

  ByteBuf(_size):
     _byteBuffers = ByteData.sublistView(Uint8List(_size), 0);

  writeNullBoolean(bool? value) {
    ensureSize(2);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _byteBuffers.setInt8(_writeOffset++, 1);
    var v = value ? 1 : 0;
    _byteBuffers.setInt8(_writeOffset++, v);
  }

  writeNullByte(int? value) {
    ensureSize(2);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _checkByte(value!);
    _byteBuffers.setInt8(_writeOffset++, 1);
    _byteBuffers.setInt8(_writeOffset++, value);
  }

  writeNullShort(int? value) {
    ensureSize(3);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _checkShort(value!);
    _byteBuffers.setInt8(_writeOffset++, 1);
    _byteBuffers.setInt16(_writeOffset, value, Endian.big);
    _writeOffset += 2;
  }

  writeNullInt(int? value) {
    ensureSize(5);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _checkInt(value!);
    _byteBuffers.setInt8(_writeOffset++, 1);
    _byteBuffers.setInt32(_writeOffset, value!, Endian.little);
    _writeOffset += 4;
  }

  writeVarInt(int? value) {
    ensureSize(6);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _checkInt(value!);
    _byteBuffers.setInt8(_writeOffset++, 1);

    var values = intToBytes(value);
    for (var v in values) {
      _byteBuffers.setUint8(_writeOffset++, v);
    }
  }

  writeNullLong(int? value) {
    ensureSize(9);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _checkLong(value!);
    _byteBuffers.setInt8(_writeOffset++, 1);
    _byteBuffers.setInt64(_writeOffset, value, Endian.little);
    _writeOffset += 8;
  }

  writeNullLong2(int? value) {
    ensureSize(9);
    if (value == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _checkLong(value!);
    _byteBuffers.setInt8(_writeOffset++, 1);
    var arrayBuffer = longToBytes(value!);
    writeArrayBuffer(arrayBuffer);
  }

  writeNullString(String? str) {
    ensureSize(5);
    if (str == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _byteBuffers.setInt8(_writeOffset++, 1);
    if (str == '') {
      _byteBuffers.setInt32(_writeOffset, 0, Endian.little);
      _writeOffset += 4;
    } else {
      var uint8List = utf8.encode(str);
      ensureSize(uint8List.length + 4);
      _byteBuffers.setInt32(_writeOffset, uint8List.length, Endian.little);
      _writeOffset += 4;
      writeArrayBuffer(uint8List);
    }
  }

  writeNullDate(DateTime? dateTime) {
    ensureSize(8);
    if (dateTime == null) {
      _byteBuffers.setInt8(_writeOffset++, 0);
      return;
    }
    _byteBuffers.setInt8(_writeOffset++, 1);
    var time = dateTime!.millisecond;
    _byteBuffers.setInt64(_writeOffset, time, Endian.little);
    _writeOffset += 8;
  }

  writeArrayBuffer(Uint8List uint8List) {
    int length = uint8List.length;
    if (length < 1) {
      print('eeeeeeeeeeeeeeeeeeeeeeeeeeeee: $length');
      return;
    }
    ensureSize(length);
    for (var i=0; i<length; i++) {
      _byteBuffers.setUint8(_writeOffset++, uint8List[i]);
    }
  }

  writeByte(int value) {
    ensureSize(1);
    _checkByte(value);
    _byteBuffers.setInt8(_writeOffset++, value);
  }

  writeInt(int value) {
    ensureSize(4);
    _checkInt(value);
    _byteBuffers.setInt32(_writeOffset, value, Endian.big);
    _writeOffset += 4;
  }

  get writeOffset => _writeOffset;

  get readOffset => _readOffset;

  getBuffer() {
    return _byteBuffers;
  }

  Uint8List getArrayBuffer() {
    return _byteBuffers.buffer.asUint8List(_readOffset, _writeOffset);
  }

  bool? readNullBoolean() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    checkReadSize(1);
    v = _byteBuffers.getInt8(_readOffset++);
    return v == 1 ? true : false;
  }

  int? readNullByte() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    checkReadSize(1);
    return _byteBuffers.getInt8(_readOffset++);
  }

  int readUInt8() {
    checkReadSize(1);
    return _byteBuffers.getUint8(_readOffset++);
  }

  int? readNullShort() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
      if (v == 0) {
      return null;
    }
    checkReadSize(2);
    var value = _byteBuffers.getInt16(_readOffset, Endian.big);
    _readOffset += 2;
    return value;
  }

  int? readNullInt() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    checkReadSize(4);
    var value = _byteBuffers.getInt32(_readOffset, Endian.little);
    _readOffset+=4;
    return value;
  }

  int readInt() {
    checkReadSize(4);
    var value = _byteBuffers.getInt32(_readOffset, Endian.big);
    _readOffset+=4;
    return value;
  }

  int readByte() {
    checkReadSize(1);
    var value = _byteBuffers.getInt8(_readOffset++);
    return value;
  }

  int? readVarInt() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    // todo
    throw Exception('暂时不支持这种方法');
  }

  int? readNullLong() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    checkReadSize(8);
    var value = _byteBuffers.getInt64(_readOffset, Endian.little);
    _readOffset+=8;
    return value;
  }

  // int? readNullLong2() {
  //   checkReadSize(1);
  //   var v = _byteBuffers.getInt8(_readOffset++);
  //   if (v == 0) {
  //     return null;
  //   }
  //   checkReadSize(8);
  //   var arrayBuffer = _byteBuffers.
  //   var value = bytesToLong(arrayBuffer)
  //   readOffset += 8
  //   return value
  // }

  String? readNullString() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    checkReadSize(4);
    var value = _byteBuffers.getInt32(_readOffset, Endian.little);
    _readOffset+=4;
    if (value > 0) {
      checkReadSize(value);
      var buffer = slice(value);
      var str = uint8ArrayToString(buffer);
      return str;
    } else {
      return '';
    }
  }

  DateTime? readNullDate() {
    checkReadSize(1);
    var v = _byteBuffers.getInt8(_readOffset++);
    if (v == 0) {
      return null;
    }
    checkReadSize(8);
    var time = _byteBuffers.getInt64(_readOffset, Endian.little);
    _readOffset+=8;
    var date = DateTime.fromMillisecondsSinceEpoch(time);
    return date;
  }

  setBoolean(int position, bool value) {
    _checkInt(position);
    _byteBuffers.setInt8(position, value ? 1 : 0);
  }

  setByte(int position, int value) {
    _checkByte(value);
    _checkInt(position);
    _byteBuffers.setInt8(position, value);
  }

  setShort(int position, int value) {
    _checkShort(value);
    _checkInt(position);
    _byteBuffers.setInt16(position, value, Endian.big);
  }

  setInt(int position, int value) {
    _byteBuffers.setInt32(position, value, Endian.big);
  }



  markReadIndex() {
    _markReadIndex = _readOffset;
  }

  resetMarkReadIndex() {
    _readOffset = _markReadIndex;
  }

  markWriteIndex() {
    _markWriterIndex = _writeOffset;
  }

  resetMarkWriteIndex() {
    _writeOffset = _markWriterIndex;
  }

  // 可读数据大小
  int readableSize() {
    var size = _writeOffset - _readOffset;
    if (size >= 0) return size;
    throw Exception("writeOfsset和readOffset有误");
  }

  Uint8List slice(int length) {
    checkReadSize(length);
    var arrayBuffer = _byteBuffers.buffer.asUint8List().sublist(_readOffset, _readOffset + length);
    _readOffset += length;
    return arrayBuffer;
  }

  String uint8ArrayToString(Uint8List array) {
    return utf8.decode(array);
  }

  checkReadSize(int size) {
    var canReadSize = readableSize();
    if (canReadSize < size) {
    throw Exception("剩余可读字节${canReadSize}少于${size}");
    }
  }

  ensureSize(int length) {
    if(length < 1) throw Exception("参数错误");
    if (_size - writeOffset > length) return;
    double size = _size * 1.5;
    var newSize = size.toInt();
    if (newSize < _size + length) {
      newSize = _size + length;
    }
    Uint8List list = Uint8List(newSize);
    Uint8List byteBuffer = _byteBuffers.buffer.asUint8List();
    list.setRange(0, _writeOffset, byteBuffer.sublist(0, _writeOffset));

    _byteBuffers = ByteData.sublistView(list);
  }

  shake() {
    if (_readOffset < 1) return;
    Uint8List byteBuffer = _byteBuffers.buffer.asUint8List();
    byteBuffer.setRange(0, _writeOffset - _readOffset, byteBuffer.sublist(_readOffset, _writeOffset));
    _writeOffset -= _readOffset;
    _readOffset = 0;
  }

  _checkByte(int value) {
    if(value < -128 || value > 127) {
      throw Exception('value值必须在-128到127之间');
    }
  }

  _checkShort(int value) {
    if(value < -32768 || value > 32767) {
      throw Exception('value值必须在-32768到32767之间');
    }
  }

  _checkInt(int value) {
    if(value < -9223372036854775808 || value > 9223372036854775807) {
      throw Exception('value值必须在-9223372036854775807之间');
    }
  }

  _checkLong(int value) {
    if(value < -9223372036854775808 || value > 9223372036854775807) {
      throw Exception('value值必须在-9223372036854775807之间');
    }
  }
}

Uint8List intToBytes(int intValue) {
  var list = Uint8List(4);
  //遍历列表中的每个正整数。
  while (intValue > 0) {
  var byteOf = intValue & 0x7f; //得到正整数的低7位。
  int i = 0;
  if (intValue < 128) {
    //如果n值已经能由7位表示，则该字节是最后一个字节。
    byteOf = intValue + 128; //将该字节的最高位置为1。
    list[i++] = byteOf;
  } else {
    list[i++] = byteOf;
  }
    intValue = intValue >> 7;
  }
  return list;
}

Uint8List longToBytes(int num) {
  var bytes = Uint8List(8);
  int i = 0;
  bytes[i++] = num & 0xff;
  bytes[i++] = num>>8 & 0xff;
  bytes[i++] = num>>16 & 0xff;
  bytes[i++] = num>>24 & 0xff;
  bytes[i++] = num>>32 & 0xff;
  bytes[i++] = num>>40 & 0xff;
  bytes[i++] = num>>48 & 0xff;
  bytes[i++] = num>>56 & 0xff;
  return bytes;
}

int bytesToLong(Uint8List bytes) {
  var result = 0;
  int i = 0;
  result += bytes[i++];
  result += (bytes[i++]<<8);
  result += (bytes[i++]<<16);
  result += (bytes[i++]<<24);
  result += (bytes[i++]<<32);
  result += (bytes[i++]<<40);
  result += (bytes[i++]<<48);
  result += (bytes[i++]<<56);

  return result;
}